package com.zrkizzy.module.system.service.menu;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.zrkizzy.common.core.constant.HttpConst;
import com.zrkizzy.common.core.constant.TimeConst;
import com.zrkizzy.common.core.domain.response.OptionsVO;
import com.zrkizzy.common.core.utils.StringUtil;
import com.zrkizzy.common.core.utils.bean.BeanCopyUtil;
import com.zrkizzy.common.models.domain.system.menu.Menu;
import com.zrkizzy.common.models.dto.system.menu.MenuDTO;
import com.zrkizzy.common.models.enums.system.log.OperateType;
import com.zrkizzy.common.models.query.system.menu.MenuQuery;
import com.zrkizzy.common.models.vo.system.menu.MenuVO;
import com.zrkizzy.common.models.vo.system.menu.route.MetaVO;
import com.zrkizzy.common.models.vo.system.menu.route.RouterVO;
import com.zrkizzy.common.redis.service.IRedisService;
import com.zrkizzy.module.security.utils.SecurityUtil;
import com.zrkizzy.module.system.exception.SystemErrorCode;
import com.zrkizzy.module.system.mapper.menu.MenuMapper;
import com.zrkizzy.system.facade.annotation.OperationLog;
import com.zrkizzy.system.facade.service.menu.IMenuService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.map.HashedMap;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;

import java.util.*;

import static com.zrkizzy.common.redis.enums.RedisKey.SYSTEM_MENU_KEY;

/**
 * 菜单业务逻辑接口实现类
 *
 * @author zhangrongkang
 * @since 2023/4/17
 */
@Slf4j
@Service
public class MenuServiceImpl implements IMenuService {
    @Autowired
    private SecurityUtil securityUtil;

    @Autowired
    private IRedisService redisService;

    @Autowired
    private MenuMapper menuMapper;

    /**
     * 获取菜单数据
     *
     * @return 当前登录用户的菜单
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public List<RouterVO> getRoutes() {
        // 使用用户角色ID来拼接Key
        String key = SYSTEM_MENU_KEY.getKey() + securityUtil.getLoginUserRoleId();
        // 如果菜单数据不为空则
        List<RouterVO> buildRoutes = redisService.getList(key, RouterVO.class);
        if (!CollectionUtils.isEmpty(buildRoutes)) {
            // 从Redis中获取菜单集合
            return buildRoutes;
        }
        // 根据用户角色获取对应数据
        List<Menu> menus = securityUtil.isAdmin() ?
                menuMapper.getAllRoutes() :
                menuMapper.getRoutes(securityUtil.getLoginUserRole());
        // 封装菜单数据
        buildRoutes = buildRoutes(menus);
        // 存储菜单数据到Redis
        if (!CollectionUtils.isEmpty(buildRoutes)) {
            redisService.setList(key, buildRoutes, TimeConst.TWO_HOUR);
        }
        // 返回当前用户的菜单数据
        return buildRoutes;
    }

    /**
     * 构建菜单数据
     *
     * @param menus 用户所有菜单
     * @return 菜单数据
     */
    private List<RouterVO> buildRoutes(List<Menu> menus) {
        // 封装子菜单
        List<Menu> menuList = setMenuChildren(menus, 0L);
        // 返回路由集合
        List<RouterVO> routes = new ArrayList<>();
        // 遍历用户菜单集合转为路由集合
        for (Menu menu : menuList) {
            // 根据当前菜单构建路由
            RouterVO routerVO = buildRouteVO(menu);
            routes.add(routerVO);
        }
        // 排序菜单并返回
        Collections.sort(routes);
        return routes;
    }

    /**
     * 获取当前子菜单
     *
     * @param children 子菜单集合
     * @return 子菜单集合
     */
    private List<RouterVO> getRouterChildren(List<Menu> children) {
        // 没有子菜单则返回
        if (CollectionUtils.isEmpty(children)) {
            return null;
        }
        // 返回路由集合
        List<RouterVO> childRoutes = new ArrayList<>();
        // 遍历用户菜单集合转为路由集合
        for (Menu menu : children) {
            childRoutes.add(buildRouteVO(menu));
        }
        // 封装子菜单排序后返回
        Collections.sort(childRoutes);
        return childRoutes;
    }

    /**
     * 根据菜单对象构建路由返回对象
     *
     * @param menu 菜单对象
     * @return 路由返回对象
     */
    private RouterVO buildRouteVO(Menu menu) {
        // 根据当前菜单构建路由
        return RouterVO.builder()
                // 组件名称 => path首字母转大写
                .name(getRouterName(menu.getPath()))
                .component(getRouterComponent(menu))
                .hidden(menu.getVisible())
                // 拼接组件访问路径
                .path(getRouterPath(menu))
                .children(getRouterChildren(menu.getChildren()))
                .redirect(getRouterRedirect(menu.getPath(), menu.getChildren()))
                .sort(menu.getSort())
                .meta(getRouterMeta(menu))
                .build();
    }

    /**
     * 构建路由对应组件名称
     *
     * @param path 访问路径
     * @return 路由对应组件名称
     */
    private String getRouterName(String path) {
        // 如果是外链则去除HTTP或HTTPS
        if (path.contains(HttpConst.HTTPS_PREFIX) || path.contains(HttpConst.HTTP_PREFIX)) {
            path = path.replace(HttpConst.HTTPS_PREFIX, "").replace(HttpConst.HTTP_PREFIX, "");
        }
        // 1. 清除第一个 "/" 后的内容
        if (path.contains("/")) {
            path = path.substring(0, path.indexOf("/"));
        }
        // 2. 首字母转大写
        path = StringUtils.capitalize(path);
        // 3. 下划线转驼峰
        path = StringUtil.minusToUpHump(path);
        return path;
    }

    /**
     * 构建路由元数据信息
     *
     * @param menu 菜单信息对象
     * @return 路由元信息数据对象
     */
    private MetaVO getRouterMeta(Menu menu) {
        return MetaVO.builder()
                // 菜单名称
                .title(menu.getName())
                // 菜单图标
                .icon(menu.getIcon())
                // 菜单是否缓存
                .noCache(menu.getIsCache())
                // 路由外链
                .link(getRouterLink(menu))
                // 是否激活菜单
                .activeMenu(menu.getActiveMenu()).build();
    }

    /**
     * 配置路由返回对象是否重定向
     *
     * @param path 父菜单路径
     * @param children 子菜单
     * @return String 是否重定向
     */
    private String getRouterRedirect(String path, List<Menu> children) {
        if (CollectionUtils.isEmpty(children)) {
            return null;
        }
        return "/" + path + "/" + children.getFirst().getPath();
    }

    /**
     * 递归封装子菜单
     *
     * @param menus 所有菜单集合
     * @param parentId 父ID
     */
    private List<Menu> setMenuChildren(List<Menu> menus, Long parentId) {
        // 存储所有菜单
        List<Menu> childrenList = new ArrayList<>();
        // 遍历所有路由对象集合
        for (Menu menu : menus) {
            // 菜单对象父ID与当前父ID一致
            if (menu.getParentId().equals(parentId)) {
                childrenList.add(menu);
            }
        }
        // 递归设置所有子菜单
        for (Menu menu : childrenList) {
            // 设置所有子菜单
            menu.setChildren(setMenuChildren(menus, menu.getId()));
        }
        // 返回封装好的菜单
        return childrenList;
    }

    /**
     * 获取路由外链
     *
     * @param menu 菜单对象
     * @return 路由外链
     */
    private String getRouterLink(Menu menu) {
        // 外链则返回链接地址
        return menu.getIsLink()? menu.getPath() : null;
    }

    /**
     * 获取组件访问路径
     *
     * @param menu 菜单对象
     * @return 当前菜单对应访问路径
     */
    private String getRouterPath(Menu menu) {
        // 外链直接返回
        if (menu.getPath().startsWith(HttpConst.HTTP_PREFIX) || menu.getPath().startsWith(HttpConst.HTTPS_PREFIX)) {
            return menu.getPath();
        }
        // 父菜单拼接路径，子菜单直接返回
        return menu.getParentId().equals(HttpConst.PARENT_ID) ? "/" + menu.getPath() : menu.getPath();
    }

    /**
     * 获取路由组件
     *
     * @param menu 菜单对象
     * @return 当前菜单对应组件
     */
    private String getRouterComponent(Menu menu) {
        // 父菜单则直接返回Layout
        return !StringUtils.hasLength(menu.getComponent()) ? HttpConst.LAYOUT : menu.getComponent();
    }

    /**
     * 获取菜单列表
     *
     * @param menuQuery 菜单信息查询对象
     * @return 菜单列表
     */
    @Override
    public List<MenuVO> listMenu(MenuQuery menuQuery) {
        // 获取菜单集合
        List<Menu> menuList = securityUtil.isAdmin() ?
                // 角色为管理员则获取全部菜单
                menuMapper.listAllMenus(menuQuery) :
                // 否则根据角色获取对应菜单
                menuMapper.listMenus(menuQuery, securityUtil.getLoginUserRole());
        // 复制集合
        List<Menu> menus = setMenuChildren(menuList, 0L);
        if (StringUtils.hasLength(menuQuery.getName()) ||
                null != menuQuery.getStatus() ||
                (!CollectionUtils.isEmpty(menuQuery.getDataRange()) && menuQuery.getDataRange().size() == 2)) {
            // 如果封装后的对象为空
            if (CollectionUtils.isEmpty(menus)) {
                // 直接返回查询的全部数据
                return BeanCopyUtil.copyList(menuList, MenuVO.class);
            }
            // 判断是否有没有被封装的子菜单
            int count = 0;
            Map<Long, Menu> map = new HashedMap<>();
            // 获取所有菜单的数量
            for (Menu menu : menus) {
                // 计算所有子菜单数量
                count += menu.getChildren().size();
                // 添加当前菜单信息到Map集合中
                map.put(menu.getId(), menu);
            }
            // 如果封装子菜单后的所有菜单数量与查询出的不一致说明有子菜单被过滤
            if (count + menus.size() != menuList.size()) {
                // 添加子菜单到集合中
                for (Menu menu : menuList) {
                    // 如果集合中不存在当前菜单对象的父菜单ID、当前菜单对象的ID，则说明在设置子菜单时将子菜单剔除出了集合
                    if (!map.containsKey(menu.getParentId()) && !map.containsKey(menu.getId())) {
                        // 添加被剔除的子菜单
                        menus.add(menu);
                    }
                }
            }
        }
        // 定义返回结果
        List<MenuVO> result = new ArrayList<>();
        // 封装当前菜单列表
        for (Menu menu : menus) {
            // 根据具体的Menu对象来构建MenuVO返回对象
            MenuVO menuVO = buildMenuVO(menu);
            result.add(menuVO);
        }
        Collections.sort(result);
        return result;
    }

    /**
     * 获取菜单选项
     *
     * @return 菜单选项集合
     */
    @Override
    public List<OptionsVO> listMenuOptions() {
        // 获取当前登录用户角色
        Long roleId = securityUtil.getLoginUserRoleId();
        List<OptionsVO> options = null;
        // 如果当前登录用户是管理员
        if (securityUtil.isAdmin()) {
            options = menuMapper.listAllMenuOptions();
        } else {
            options = menuMapper.listMenuOptions(roleId);
        }
        options.addFirst(new OptionsVO(0L, "主菜单"));
        // 返回查询的
        return options;
    }

    /**
     * 获取指定菜单数据
     *
     * @param menuId 菜单ID
     * @return 菜单数据返回对象
     */
    @Override
    public MenuVO getMenuById(Long menuId) {
        Menu menu = menuMapper.selectById(menuId);
        return BeanCopyUtil.copy(menu, MenuVO.class);
    }

    /**
     * 保存菜单信息
     *
     * @param menuDTO 菜单数据传输对象
     * @return 是否保存成功
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public Boolean save(MenuDTO menuDTO) {
        // 更新菜单前获取到所有菜单索引
        Set<String> menuKeys = redisService.scanKeys(SYSTEM_MENU_KEY.getKey());
        for (String key : menuKeys) {
            redisService.del(key);
        }
        // 根据ID判断添加或删除
        return null != menuDTO.getId() ? update(menuDTO) : insert(menuDTO);
    }

    /**
     * 删除指定菜单
     *
     * @param menuId 菜单ID
     * @return 是否删除成功
     */
    @Transactional(rollbackFor = Exception.class)
    @Override
    public Boolean delete(Long menuId) {
        // 检查是否可以删除
        checkMenuDelete(menuId);
        return menuMapper.deleteById(menuId) == 1;
    }

    /**
     * 清除菜单缓存数据
     *
     * @param result 上一次操作是否执行成功
     */
    @Override
    public void clearMenuCache(Boolean result) {
        if (result) {
            redisService.clearKeys(SYSTEM_MENU_KEY.getKey());
        }
    }

    /**
     * 检查当前菜单是否可以删除
     *
     * @param menuId 菜单ID
     */
    private void checkMenuDelete(Long menuId) {
        // 定义查询条件
        QueryWrapper<Menu> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("parent_id", menuId);
        // 查询当前要删除的菜单是否有子菜单
        if (menuMapper.selectCount(queryWrapper) > 0) {
            // 抛出目录不为空不允许删除异常
            throw SystemErrorCode.DIRECTORY_NOT_EMPTY.exception();
        }
    }

    /**
     * 添加菜单数据
     *
     * @param menuDTO 菜单数据传输对象
     * @return 是否添加成功
     */
    @OperationLog(type = OperateType.ADD)
    private Boolean insert(MenuDTO menuDTO) {
        Menu menu = BeanCopyUtil.copy(menuDTO, Menu.class);
        return menuMapper.insert(menu) == 1;
    }

    /**
     * 更新菜单数据
     *
     * @param menuDTO 菜单数据传输对象
     * @return 是否更新成功
     */
    @OperationLog(type = OperateType.UPDATE)
    private Boolean update(MenuDTO menuDTO) {
        Menu menu = BeanCopyUtil.copy(menuDTO, Menu.class);
        return menuMapper.updateById(menu) == 1;
    }

    /**
     * 构建菜单返回对象
     *
     * @param menu 菜单数据对象
     * @return 菜单数据返回对象
     */
    private MenuVO buildMenuVO(Menu menu) {
        // 复制菜单返回对象
        MenuVO menuVO = BeanCopyUtil.copy(menu, MenuVO.class);
        // 设置子菜单数据
        menuVO.setChildren(getMenuChildren(menu.getChildren()));
        return menuVO;
    }

    /**
     * 设置子菜单数据
     *
     * @param menus 菜单集合
     * @return 菜单数据返回对象集合
     */
    private List<MenuVO> getMenuChildren(List<Menu> menus) {
        // 如果子菜单为空则返回
        if (CollectionUtils.isEmpty(menus)) {
            return null;
        }
        // 转集合
        List<MenuVO> result = BeanCopyUtil.copyList(menus, MenuVO.class);
        // 给排序子菜单
        Collections.sort(result);
        return result;
    }

}
